Many sites are using WordPress profile fields to add additional information about their users. WordPress profile fields can also be used in author boxes under each article. In this tutorial you will learn how to add them easily with object oriented programming and WordPress hooks.
How to extend the WordPress Profile
WordPress profile is a settings page in the admin area that is used to edit the user’s information. This can be done by an administrator or by the user. The user can only edit their own profile but the administrator can edit all of them of course. We will extend the WordPress profile page by using WordPress hooks:
- show_user_profile
- edit_user_profile
Those hooks will show our own HTML at the bottom of the page. We will also need to save the information we want to enter so we will need to use two additional action hooks that are called upon saving the profile data:
- personal_options_update
- edit_user_profile_update
Creating WordPress Profile Fields
For creating our profile fields we will use an abstract class that we have already coded in a previous tutorial where we have learned how to create WordPress menu pages with OOP. If you did not read that article I do advise you to read it so that you can understand the abstract class and how to use it.
We will create our own class for handling profile fields by extending the abstract class and modified a few methods. If you have the code for our abstract class jump to the next chapter below, but if you don’t have the code for the abstract class here you can copy it:
<?php | |
abstract class WordPressSettings { | |
/** | |
* ID of the settings | |
* @var string | |
*/ | |
public $settings_id = ''; | |
/** | |
* Tabs for the settings page | |
* @var array | |
*/ | |
public $tabs = array( | |
'general' => 'General' ); | |
/** | |
* Settings from database | |
* @var array | |
*/ | |
protected $settings = array(); | |
/** | |
* Array of fields for the general tab | |
* array( | |
* 'tab_slug' => array( | |
* 'field_name' => array(), | |
* ), | |
* ) | |
* @var array | |
*/ | |
protected $fields = array(); | |
/** | |
* Data gotten from POST | |
* @var array | |
*/ | |
protected $posted_data = array(); | |
/** | |
* Get the settings from the database | |
* @return void | |
*/ | |
public function init_settings() { | |
$this->settings = (array) get_option( $this->settings_id ); | |
foreach ( $this->fields as $tab_key => $tab ) { | |
foreach ( $tab as $name => $field ) { | |
if( isset( $this->settings[ $name ] ) ) { | |
$this->fields[ $tab_key ][ $name ]['default'] = $this->settings[ $name ]; | |
} | |
} | |
} | |
} | |
/** | |
* Save settings from POST | |
* @return [type] [description] | |
*/ | |
public function save_settings(){ | |
$this->posted_data = $_POST; | |
if( empty( $this->settings ) ) { | |
$this->init_settings(); | |
} | |
foreach ($this->fields as $tab => $tab_data ) { | |
foreach ($tab_data as $name => $field) { | |
$this->settings[ $name ] = $this->{ 'validate_' . $field['type'] }( $name ); | |
} | |
} | |
update_option( $this->settings_id, $this->settings ); | |
} | |
/** | |
* Gets and option from the settings API, using defaults if necessary to prevent undefined notices. | |
* | |
* @param string $key | |
* @param mixed $empty_value | |
* @return mixed The value specified for the option or a default value for the option. | |
*/ | |
public function get_option( $key, $empty_value = null ) { | |
if ( empty( $this->settings ) ) { | |
$this->init_settings(); | |
} | |
// Get option default if unset. | |
if ( ! isset( $this->settings[ $key ] ) ) { | |
$form_fields = $this->fields; | |
foreach ( $this->tabs as $tab_key => $tab_title ) { | |
if( isset( $form_fields[ $tab_key ][ $key ] ) ) { | |
$this->settings[ $key ] = isset( $form_fields[ $tab_key ][ $key ]['default'] ) ? $form_fields[ $tab_key ][ $key ]['default'] : ''; | |
} | |
} | |
} | |
if ( ! is_null( $empty_value ) && empty( $this->settings[ $key ] ) && '' === $this->settings[ $key ] ) { | |
$this->settings[ $key ] = $empty_value; | |
} | |
return $this->settings[ $key ]; | |
} | |
/** | |
* Validate text field | |
* @param string $key name of the field | |
* @return string | |
*/ | |
public function validate_text( $key ){ | |
$text = $this->get_option( $key ); | |
if ( isset( $this->posted_data[ $key ] ) ) { | |
$text = wp_kses_post( trim( stripslashes( $this->posted_data[ $key ] ) ) ); | |
} | |
return $text; | |
} | |
/** | |
* Validate textarea field | |
* @param string $key name of the field | |
* @return string | |
*/ | |
public function validate_textarea( $key ){ | |
$text = $this->get_option( $key ); | |
if ( isset( $this->posted_data[ $key ] ) ) { | |
$text = wp_kses( trim( stripslashes( $this->posted_data[ $key ] ) ), | |
array_merge( | |
array( | |
'iframe' => array( 'src' => true, 'style' => true, 'id' => true, 'class' => true ) | |
), | |
wp_kses_allowed_html( 'post' ) | |
) | |
); | |
} | |
return $text; | |
} | |
/** | |
* Validate WPEditor field | |
* @param string $key name of the field | |
* @return string | |
*/ | |
public function validate_wpeditor( $key ){ | |
$text = $this->get_option( $key ); | |
if ( isset( $this->posted_data[ $key ] ) ) { | |
$text = wp_kses( trim( stripslashes( $this->posted_data[ $key ] ) ), | |
array_merge( | |
array( | |
'iframe' => array( 'src' => true, 'style' => true, 'id' => true, 'class' => true ) | |
), | |
wp_kses_allowed_html( 'post' ) | |
) | |
); | |
} | |
return $text; | |
} | |
/** | |
* Validate select field | |
* @param string $key name of the field | |
* @return string | |
*/ | |
public function validate_select( $key ) { | |
$value = $this->get_option( $key ); | |
if ( isset( $this->posted_data[ $key ] ) ) { | |
$value = stripslashes( $this->posted_data[ $key ] ); | |
} | |
return $value; | |
} | |
/** | |
* Validate radio | |
* @param string $key name of the field | |
* @return string | |
*/ | |
public function validate_radio( $key ) { | |
$value = $this->get_option( $key ); | |
if ( isset( $this->posted_data[ $key ] ) ) { | |
$value = stripslashes( $this->posted_data[ $key ] ); | |
} | |
return $value; | |
} | |
/** | |
* Validate checkbox field | |
* @param string $key name of the field | |
* @return string | |
*/ | |
public function validate_checkbox( $key ) { | |
$status = ''; | |
if ( isset( $this->posted_data[ $key ] ) && ( 1 == $this->posted_data[ $key ] ) ) { | |
$status = '1'; | |
} | |
return $status; | |
} | |
/** | |
* Adding fields | |
* @param array $array options for the field to add | |
* @param string $tab tab for which the field is | |
*/ | |
public function add_field( $array, $tab = 'general' ) { | |
$allowed_field_types = array( | |
'text', | |
'textarea', | |
'wpeditor', | |
'select', | |
'radio', | |
'checkbox' ); | |
// If a type is set that is now allowed, don't add the field | |
if( isset( $array['type'] ) &&$array['type'] != '' && ! in_array( $array['type'], $allowed_field_types ) ){ | |
return; | |
} | |
$defaults = array( | |
'name' => '', | |
'title' => '', | |
'default' => '', | |
'placeholder' => '', | |
'type' => 'text', | |
'options' => array(), | |
'default' => '', | |
'desc' => '', | |
); | |
$array = array_merge( $defaults, $array ); | |
if( $array['name'] == '' ) { | |
return; | |
} | |
foreach ( $this->fields as $tabs ) { | |
if( isset( $tabs[ $array['name'] ] ) ) { | |
trigger_error( 'There is alreay a field with name ' . $array['name'] ); | |
return; | |
} | |
} | |
// If there are options set, then use the first option as a default value | |
if( ! empty( $array['options'] ) && $array['default'] == '' ) { | |
$array_keys = array_keys( $array['options'] ); | |
$array['default'] = $array_keys[0]; | |
} | |
if( ! isset( $this->fields[ $tab ] ) ) { | |
$this->fields[ $tab ] = array(); | |
} | |
$this->fields[ $tab ][ $array['name'] ] = $array; | |
} | |
/** | |
* Adding tab | |
* @param array $array options | |
*/ | |
public function add_tab( $array ) { | |
$defaults = array( | |
'slug' => '', | |
'title' => '' ); | |
$array = array_merge( $defaults, $array ); | |
if( $array['slug'] == '' || $array['title'] == '' ){ | |
return; | |
} | |
$this->tabs[ $array['slug'] ] = $array['title']; | |
} | |
/** | |
* Rendering fields | |
* @param string $tab slug of tab | |
* @return void | |
*/ | |
public function render_fields( $tab ) { | |
if( ! isset( $this->fields[ $tab ] ) ) { | |
echo '<p>' . __( 'There are no settings on these page.', 'textdomain' ) . '</p>'; | |
return; | |
} | |
foreach ( $this->fields[ $tab ] as $name => $field ) { | |
$this->{ 'render_' . $field['type'] }( $field ); | |
} | |
} | |
/** | |
* Render text field | |
* @param string $field options | |
* @return void | |
*/ | |
public function render_text( $field ){ | |
extract( $field ); | |
?> | |
<tr> | |
<th> | |
<label for="<?php echo $name; ?>"><?php echo $title; ?></label> | |
</th> | |
<td> | |
<input type="<?php echo $type; ?>" name="<?php echo $name; ?>" id="<?php echo $name; ?>" value="<?php echo $default; ?>" placeholder="<?php echo $placeholder; ?>" /> | |
<?php if( $desc != '' ) { | |
echo '<p class="description">' . $desc . '</p>'; | |
}?> | |
</td> | |
</tr> | |
<?php | |
} | |
/** | |
* Render textarea field | |
* @param string $field options | |
* @return void | |
*/ | |
public function render_textarea( $field ){ | |
extract( $field ); | |
?> | |
<tr> | |
<th> | |
<label for="<?php echo $name; ?>"><?php echo $title; ?></label> | |
</th> | |
<td> | |
<textarea name="<?php echo $name; ?>" id="<?php echo $name; ?>" placeholder="<?php echo $placeholder; ?>" ><?php echo $default; ?></textarea> | |
<?php if( $desc != '' ) { | |
echo '<p class="description">' . $desc . '</p>'; | |
}?> | |
</td> | |
</tr> | |
<?php | |
} | |
/** | |
* Render WPEditor field | |
* @param string $field options | |
* @return void | |
*/ | |
public function render_wpeditor( $field ){ | |
extract( $field ); | |
?> | |
<tr> | |
<th> | |
<label for="<?php echo $name; ?>"><?php echo $title; ?></label> | |
</th> | |
<td> | |
<?php wp_editor( $default, $name, array('wpautop' => false) ); ?> | |
<?php if( $desc != '' ) { | |
echo '<p class="description">' . $desc . '</p>'; | |
}?> | |
</td> | |
</tr> | |
<?php | |
} | |
/** | |
* Render select field | |
* @param string $field options | |
* @return void | |
*/ | |
public function render_select( $field ) { | |
extract( $field ); | |
?> | |
<tr> | |
<th> | |
<label for="<?php echo $name; ?>"><?php echo $title; ?></label> | |
</th> | |
<td> | |
<select name="<?php echo $name; ?>" id="<?php echo $name; ?>" > | |
<?php | |
foreach ($options as $value => $text) { | |
echo '<option ' . selected( $default, $value, false ) . ' value="' . $value . '">' . $text . '</option>'; | |
} | |
?> | |
</select> | |
<?php if( $desc != '' ) { | |
echo '<p class="description">' . $desc . '</p>'; | |
}?> | |
</td> | |
</tr> | |
<?php | |
} | |
/** | |
* Render radio | |
* @param string $field options | |
* @return void | |
*/ | |
public function render_radio( $field ) { | |
extract( $field ); | |
?> | |
<tr> | |
<th> | |
<label for="<?php echo $name; ?>"><?php echo $title; ?></label> | |
</th> | |
<td> | |
<?php | |
foreach ($options as $value => $text) { | |
echo '<input name="' . $name . '" id="' . $name . '" type="'. $type . '" ' . checked( $default, $value, false ) . ' value="' . $value . '">' . $text . '</option><br/>'; | |
} | |
?> | |
<?php if( $desc != '' ) { | |
echo '<p class="description">' . $desc . '</p>'; | |
}?> | |
</td> | |
</tr> | |
<?php | |
} | |
/** | |
* Render checkbox field | |
* @param string $field options | |
* @return void | |
*/ | |
public function render_checkbox( $field ) { | |
extract( $field ); | |
?> | |
<tr> | |
<th> | |
<label for="<?php echo $name; ?>"><?php echo $title; ?></label> | |
</th> | |
<td> | |
<input <?php checked( $default, '1', true ); ?> type="<?php echo $type; ?>" name="<?php echo $name; ?>" id="<?php echo $name; ?>" value="1" placeholder="<?php echo $placeholder; ?>" /> | |
<?php echo $desc; ?> | |
</td> | |
</tr> | |
<?php | |
} | |
} |
Extending our Abstract Class
First, we will create our class with some basic attributes and a constructor method. Once we understand what we do here, we will create the other methods for rendering the fields and saving them.
<?php | |
class IBenic_WordPressUser_FieldsSettings extends WordPressSettings { | |
// Title of our section | |
protected $title = ''; | |
// Slug of our settings | |
protected $slug = ''; | |
// User ID for which we save | |
protected $user_id = 0; | |
// Constructor method | |
function __construct( $title, $slug ) { | |
$this->title = $title; | |
$this->slug = $slug; | |
$this->settings_id = $this->slug; | |
add_action( 'show_user_profile', array( $this, 'render_admin_fields' ) ); | |
add_action( 'edit_user_profile', array( $this, 'render_admin_fields' ) ); | |
add_action( 'personal_options_update', array( $this, 'save_profile_settings' ) ); | |
add_action( 'edit_user_profile_update', array( $this, 'save_profile_settings' ) ); | |
} | |
// ... | |
} |
Our attribute $title will be used for the section title on the user’s profile page. The attribute $slug will be used as the id of our settings. This will be used to save the data and also to retrieve them as we want. In out constructor method we assign the passed parameters and hook our other methods that we will define next. You can see here that we are using the hooks that we have previously mentioned.
The attribute $settings_id is an attribute inherited from the abstract class. There will also be some other methods used here that we have inherited from the abstract class so if you still do not understand from where some methods or attributes come from, please do read the article mentioned before. If you don’t have time to read that, then just remember that everything that is used here and not defined in our class, we have inherited it from the abstract class.
Let’s now define the method to render the fields:
<?php | |
class IBenic_WordPressUser_FieldsSettings extends WordPressSettings { | |
// ... | |
// Rendering the admin fields | |
public function render_admin_fields( $user ){ | |
$this->user_id = $user->ID; | |
$this->init_settings(); | |
echo '<h2>' . $this->title . '</h2>'; | |
echo '<table class="form-table">'; | |
$this->render_fields( 'general' ); | |
echo '</table>'; | |
} | |
// ... | |
} |
We are first setting the user ID. Once that is set we call an inherited method init_settings. Since the inherited method retrieves the settings using the function get_option we will need to modify it because we will work with the user meta table.
We are rendering our fields using the method render_fields by passing the parameter $tab with the value ‘general’. We are doing that because we do not have any additional tabs here and all our fields will be saved to the general tab once they get defined. If you want to use the tabs be sure to check the article mentioned before and see how to implement that.
Now that know how to render the fields, let’s modify the inherited method init_settings:
<?php | |
class IBenic_WordPressUser_FieldsSettings extends WordPressSettings { | |
// ... | |
/** | |
* Get the settings from the database | |
* @return void | |
*/ | |
public function init_settings() { | |
$user_id = $this->user_id; | |
$this->settings = get_user_meta( $user_id, $this->settings_id, true ); | |
foreach ( $this->fields as $tab_key => $tab ) { | |
foreach ( $tab as $name => $field ) { | |
if( isset( $this->settings[ $name ] ) ) { | |
$this->fields[ $tab_key ][ $name ]['default'] = $this->settings[ $name ]; | |
} | |
} | |
} | |
} | |
// ... | |
} |
The whole method here is almost the same as the one inherited. The only difference are the first two lines where we set the user id and retrieve the field data from the user meta by providing the user id and the slug (settings_id) that we have provided in the contructor method.
So we know how to render the fields and also how to retrieve the data. The only thing left to define is how to save our profile data:
<?php | |
class IBenic_WordPressUser_FieldsSettings extends WordPressSettings { | |
// ... | |
// Wrapper to get the user id and then save the settings | |
public function save_profile_settings( $user_id ) { | |
if ( !current_user_can( 'edit_user', $user_id ) ) { | |
return false; | |
} | |
$this->user_id = $user_id; | |
$this->save_settings(); | |
} | |
/** | |
* Save settings from POST | |
* @return [type] [description] | |
*/ | |
public function save_settings(){ | |
$this->posted_data = $_POST; | |
if( empty( $this->settings ) ) { | |
$this->init_settings(); | |
} | |
foreach ($this->fields as $tab => $tab_data ) { | |
foreach ($tab_data as $name => $field) { | |
$this->settings[ $name ] = $this->{ 'validate_' . $field['type'] }( $name ); | |
} | |
} | |
update_user_meta( $this->user_id, $this->settings_id, $this->settings ); | |
} | |
} |
The method save_profile_settings is used to set the user id provided by the two hooks personal_options_update and edit_user_profile_update and to call the inherited method for saving the settings. The inherited method save_settings is using the function update_option to save the data which is not what we are using for storing the profile data.
The second method is the modified version of our inherited method. The only difference from the inherited method is the last line where we use the function update_user_meta to store our profile data by providing user ID, our slug (settings_id) and the data (settings).
Now we have a functional class that can be used to create various WordPress profile fields. The type of fields that can be defined is set in the abstract class.
Using our Class to create WordPress Profile Fields
To create our profile fields we have to instantiate our class and then add fields using the inherited method add_field from the abstract class:
<?php | |
$social_fields = new IBenic_WordPressUser_FieldsSettings( 'Social Fields', 'social_fields' ); | |
$social_fields->add_field( | |
array( | |
'name' => 'facebook', | |
'title' => 'Facebook', | |
'desc' => 'Insert Full URL for Facebook' )); | |
$social_fields->add_field( | |
array( | |
'name' => 'twitter', | |
'title' => 'Twitter', | |
'type' => 'text', | |
'desc' => 'Insert Full URL for twitter' )); |
Retrieving the data
It does not make sense to not be able to retrieve that data, right? So, here is a simple example on how to retrieve the Facebook link from the user’s profile:
<?php | |
// Getting the values | |
$social_fields = get_user_meta( $user_id, 'social_fields'); | |
$facebook = $social_fields['facebook']; |
Conclusion
Today we have learned two things: how to create custom profile fields and how to reuse an existing abstract class that was previously coded. By using the WordPress hooks we could use some other methods to display the settings fields and also save the fields. In some solution you will need to modify the inherited methods and there is nothing wrong with that.
Have you ever create custom profile fields? How did you do it? Share your experience with others in the comments below:)
Become a Sponsor
Hi Igor
I like this solution, but the code is not working for me, at least using checkbox controls. The values looks like are not stored in the database after updating the profile.
Regards
Weird. Have you tried debugging what is sent in the method ‘save_settings’ to check if such checkbox data is sent at all?